/**
 * @file
 * @brief CANBUS任务
 * @author
 * + 隐星魂 (Roy Sun) <xwos@xwos.tech>
 * @copyright
 * + Copyright © 2015 xwos.tech, All Rights Reserved.
 * > Licensed under the Apache License, Version 2.0 (the "License");
 * > you may not use this file except in compliance with the License.
 * > You may obtain a copy of the License at
 * >
 * >         http://www.apache.org/licenses/LICENSE-2.0
 * >
 * > Unless required by applicable law or agreed to in writing, software
 * > distributed under the License is distributed on an "AS IS" BASIS,
 * > WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * > See the License for the specific language governing permissions and
 * > limitations under the License.
 */


#include "board/std.h"
#include <xwos/lib/xwlog.h>
#include <xwos/osal/time.h>
#include <xwos/osal/thd.h>
#include <xwos/osal/sync/cond.h>
#include <xwos/osal/lock/spinlock.h>
#include "RTD/xwds/device.h"
#include "RTD/gpio.h"
#include "FlexCAN_Ip.h"

#define LOGTAG "CANBUS"
#define CANBUS_LOG
#ifdef CANBUS_LOG
#  define canbuslogf(lv, fmt, ...) xwlogf(lv, LOGTAG, fmt, ##__VA_ARGS__)
#else
#  define canbuslogf(lv, fmt, ...)
#endif

struct canbus_driver_data {
        xwu8_t instance;
        const Flexcan_Ip_ConfigType * cfg;
        Flexcan_Ip_StateType * state;
        struct {
                struct xwos_splk lock;
                struct xwos_cond completion;
                xwer_t rc;
        } rx;
};

void canbus0_tp_init(void)
{
        xwds_gpio_set(&rtdxwds_soc, CAN0_WKUP_PORT, CAN0_WKUP_PIN);
        xwds_gpio_reset(&rtdxwds_soc, CAN0_STB_PORT, CAN0_STB_PIN);
        xwds_gpio_reset(&rtdxwds_soc, CAN0_EN_PORT, CAN0_EN_PIN);

        xwos_cthd_sleep(xwtm_ms(1));

        xwds_gpio_set(&rtdxwds_soc, CAN0_STB_PORT, CAN0_STB_PIN);
        xwds_gpio_set(&rtdxwds_soc, CAN0_EN_PORT, CAN0_EN_PIN);

        xwos_cthd_sleep(xwtm_ms(1));

        xwds_gpio_reset(&rtdxwds_soc, CAN0_WKUP_PORT, CAN0_WKUP_PIN);
}

void canbus0_gpio_init(void)
{
        xwds_gpio_req(&rtdxwds_soc, CAN0_WKUP_PORT, CAN0_WKUP_PIN);
        xwds_gpio_req(&rtdxwds_soc, CAN0_EN_PORT, CAN0_EN_PIN);
        xwds_gpio_req(&rtdxwds_soc, CAN0_STB_PORT, CAN0_STB_PIN);
}

Flexcan_Ip_IdTableType canbus0_filter_table[8] = {
        [0] = {
                .isRemoteFrame = false,
                .isExtendedFrame = false,
                .id = 0x3C0,
        },
        [1] = {
                .isRemoteFrame = false,
                .isExtendedFrame = false,
                .id = 0x394,
        },
        [2] = {
                .isRemoteFrame = false,
                .isExtendedFrame = false,
                .id = 0x0,
        },
        [3] = {
                .isRemoteFrame = false,
                .isExtendedFrame = false,
                .id = 0x0,
        },
        [4] = {
                .isRemoteFrame = false,
                .isExtendedFrame = false,
                .id = 0x0,
        },
        [5] = {
                .isRemoteFrame = false,
                .isExtendedFrame = false,
                .id = 0x0,
        },
        [6] = {
                .isRemoteFrame = false,
                .isExtendedFrame = false,
                .id = 0x0,
        },
        [7] = {
                .isRemoteFrame = false,
                .isExtendedFrame = false,
                .id = 0x0,
        },
};

struct canbus_driver_data canbus0_driver_data = {
        .instance = INST_FLEXCAN_0,
        .cfg = &FlexCAN_Config0,
        .state = &FlexCAN_State0,
};

void canbus0_can_init(void)
{
        struct canbus_driver_data * drvdata = &canbus0_driver_data;

        xwos_splk_init(&drvdata->rx.lock);
        xwos_cond_init(&drvdata->rx.completion);
        drvdata->rx.rc = XWOK;
        FlexCAN_Ip_Init(drvdata->instance, drvdata->state, drvdata->cfg);
        FlexCAN_Ip_ConfigRxFifo(drvdata->instance,
                                FLEXCAN_RX_FIFO_ID_FORMAT_A,
                                canbus0_filter_table);
        FlexCAN_Ip_SetStartMode(drvdata->instance);
}

void canbus0_callback(uint8 instance,
                      Flexcan_Ip_EventType event, uint32 buffIdx,
                      const Flexcan_Ip_StateType * state)
{
        struct canbus_driver_data * drvdata = &canbus0_driver_data;
        xwreg_t cpuirq;

        XWOS_UNUSED(instance);
        XWOS_UNUSED(buffIdx);
        XWOS_UNUSED(state);

        switch (event) {
        /* RX */
        case FLEXCAN_EVENT_RX_COMPLETE:
        case FLEXCAN_EVENT_RXFIFO_COMPLETE:
                xwos_splk_lock_cpuirqsv(&drvdata->rx.lock, &cpuirq);
                if (-EBUSY == drvdata->rx.rc) {
                        drvdata->rx.rc = XWOK;
                }
                xwos_splk_unlock_cpuirqrs(&drvdata->rx.lock, cpuirq);
                xwos_cond_broadcast(&drvdata->rx.completion);
                break;
        case FLEXCAN_EVENT_RXFIFO_WARNING:
                break;
        case FLEXCAN_EVENT_RXFIFO_OVERFLOW:
                break;
        /* TX */
        case FLEXCAN_EVENT_TX_COMPLETE:
                break;
        default:
                break;
        }
}

void canbus0_error_callback(uint8 instance,
                            Flexcan_Ip_EventType event, uint32 error,
                            const Flexcan_Ip_StateType * state)
{
        XWOS_UNUSED(instance);
        XWOS_UNUSED(error);
        XWOS_UNUSED(state);

        switch (event) {
        case FLEXCAN_EVENT_ERROR:
                break;
        case FLEXCAN_EVENT_ERROR_FAST:
                break;
        case FLEXCAN_EVENT_BUSOFF:
                break;
        case FLEXCAN_EVENT_RX_WARNING:
                break;
        case FLEXCAN_EVENT_TX_WARNING:
                break;
        default:
                break;
        }
}

xwer_t canbus0_rx(Flexcan_Ip_MsgBuffType * msg, xwtm_t to)
{
        struct canbus_driver_data * drvdata = &canbus0_driver_data;
        Flexcan_Ip_StatusType sdkrc;
        union xwos_ulock lock;
        xwsq_t lkst;
        xwreg_t cpuirq;
        xwer_t rc;

        lock.osal.splk = &drvdata->rx.lock;
        xwos_splk_lock_cpuirqsv(&drvdata->rx.lock, &cpuirq);
        drvdata->rx.rc = -EBUSY;
        sdkrc = FlexCAN_Ip_RxFifo(drvdata->instance, msg);
        switch (sdkrc) {
        case FLEXCAN_STATUS_SUCCESS:
                rc = xwos_cond_wait_to(&drvdata->rx.completion, lock, XWOS_LK_SPLK,
                                       NULL, to, &lkst);
                if (XWOK == rc) {
                        XWOS_BUG_ON((xwsq_t)XWOS_LKST_UNLOCKED == lkst);
                        rc = drvdata->rx.rc;
                        xwos_splk_unlock_cpuirqrs(&drvdata->rx.lock, cpuirq);
                } else {
                        if ((xwsq_t)XWOS_LKST_LOCKED == lkst) {
                                xwos_splk_unlock_cpuirqrs(&drvdata->rx.lock, cpuirq);
                        } else {
                                xwos_cpuirq_restore_lc(cpuirq);
                        }
                }
                break;
        case FLEXCAN_STATUS_ERROR:
                xwos_splk_unlock_cpuirqrs(&drvdata->rx.lock, cpuirq);
                rc = -EIO;
                break;
        case FLEXCAN_STATUS_BUSY:
                xwos_splk_unlock_cpuirqrs(&drvdata->rx.lock, cpuirq);
                rc = -EBUSY;
                break;
        case FLEXCAN_STATUS_BUFF_OUT_OF_RANGE:
                xwos_splk_unlock_cpuirqrs(&drvdata->rx.lock, cpuirq);
                rc = -ECHRNG;
                break;
        default:
                rc = -EBUG;
                break;
        }
        return rc;
}

xwer_t canbus0_task(void * arg)
{
        xwer_t rc = XWOK;
        Flexcan_Ip_MsgBuffType msg;

        XWOS_UNUSED(arg);

        canbus0_gpio_init();
        canbus0_can_init();
        canbus0_tp_init();

        while (!xwos_cthd_shld_stop()) {
                if (xwos_cthd_shld_frz()) {
                        rc = xwos_cthd_freeze();
                        if (rc < 0) {
                                xwos_cthd_yield();
                        }
                } else {
                        rc = canbus0_rx(&msg, XWTM_MAX);
                        if (XWOK == rc) {
                                canbuslogf(I, "CAN MSG:{id=%d}\r\n", msg.msgId);
                        } else if (-ETIMEDOUT == rc) {
                        } else if ((-EINTR == rc) || (-ERESTARTSYS == rc)) {
                        } else if (-EBUSY == rc) {
                        } else if (-ECHRNG == rc) {
                        } else if (-EIO == rc) {
                        } else {
                        }
                }
        }
        return rc;
}

#define CANBUS0_THD_PRIORITY XWOS_SKD_PRIORITY_DROP(XWOS_SKD_PRIORITY_RT_MAX, 0)
struct xwos_thd canbus0_thd;
xwos_thd_d canbus0_thdd;
__xwcc_aligned(16) xwu8_t canbus0_thd_stack[4096] = {0};

void canbus0_init(void)
{
        xwer_t rc;
        struct xwos_thd_attr attr;

        xwos_thd_attr_init(&attr);
        attr.name = "canbus0.thd";
        attr.stack = (xwstk_t *)canbus0_thd_stack;
        attr.stack_size = sizeof(canbus0_thd_stack);
        attr.priority = CANBUS0_THD_PRIORITY;
        attr.detached = true;
        attr.privileged = true;
        rc = xwos_thd_init(&canbus0_thd, &canbus0_thdd,
                           &attr,
                           canbus0_task,
                           NULL);
        if (rc < 0) {
                canbuslogf(E, "canbus0_init() ... rc\r\n", rc);
        }
}
